function gewTerrainNodeFindVertexInPreviousLevel(x,z,vertices,length)
{
  for(var i=length;i>=0;i--)
  {
    if( vertices[i*3] == x &&
        vertices[i*3+2]==z )
      return i;
  }
  return -1;
}
function gewTerrainNode(gl,clipmapSize,gridSpace,levelNum,name)
{ 
  gewNode.call(this,name);
  this.isFrustumTest = false; // should do frustum culling with this node or not?
  this.isCalcModelMat = false;
  
  this.levelNum = levelNum;
  this.clipmapSize = clipmapSize;
  this.gridSpace = gridSpace;
  this.gl = gl;
  var verticesNumLv0 = (clipmapSize+1)*(clipmapSize+1);
  var verticesNumLv1 = 3*clipmapSize*(clipmapSize/4+1);   
  //var verticesNum = verticesNumLv0*levelNum;// + (levelNum-1)*verticesNumLv1;
  var verticesNum = verticesNumLv0 + (levelNum-1)*verticesNumLv1;
  this.vertices = new WebGLFloatArray(verticesNum*3);
  this.triangleList = [];
  this.triangleNormal = [];
  this.normals = new WebGLFloatArray(verticesNum*3);
  this.glBuffers = {};
  // Generate terrain's mesh
  this.block0 = new gewTerrainBlock(clipmapSize,levelNum);
  this.block1 = new gewTerrainBlock(clipmapSize,levelNum);
  this.block2 = new gewTerrainBlock(clipmapSize,levelNum);
  this.block3 = new gewTerrainBlock(clipmapSize,levelNum);
  this.generateMesh(clipmapSize,gridSpace,levelNum);
  this.indicesNumPerBlock = this.block0.indicesNum;
  this.indicesNumPerBlockLv0 = this.block0.indicesNumLv0;
  this.indicesNumPerBlockLvn = this.block0.indicesNumLvn;
  //delete this.block0;
  //delete this.block1;
  //delete this.block2;
  //delete this.block3;
}
extend2(gewTerrainNode, gewNode);
gewTerrainNode.prototype.generateMesh = function(clipmapSize,gridSpace,levelNum)
{
  // lv0
  var offset = 0;
  var verticesFerLevel = (clipmapSize+1)*(clipmapSize+1);
  var blockSize = clipmapSize/2;
  var startPreviousLevel = clipmapSize/4;
  var endPreviousLevel = clipmapSize-startPreviousLevel;
  for(var lv=0;lv<levelNum;lv++)
  {
    var maxX = (clipmapSize/2)*gridSpace;
    var maxZ = (clipmapSize/2)*gridSpace;
    var x,z;
    var verticesArray = this.vertices;
    for(var i = 0;i<= clipmapSize;i++)
    {
      z = maxZ - (i*gridSpace);
      for(var j=0;j<= clipmapSize;j++)
      {
        var isNewVertex = true;
        x = maxX - (j*gridSpace);        
        if(lv>0)
        {          
          if(i>= startPreviousLevel&& i<= endPreviousLevel&&
             j>= startPreviousLevel&& j<= endPreviousLevel)
          {
            isNewVertex = false;
            var previousOffset = gewTerrainNodeFindVertexInPreviousLevel(x,z,verticesArray,offset);
          }
        }
        var block_i = Math.abs(i-blockSize);
        var block_j = Math.abs(j-blockSize);
        if(i<=blockSize&&
           j<=blockSize) // block 0 
        {
          if(isNewVertex)
            this.block0.level[lv].indices[block_j][block_i] = offset;
          else
            this.block0.level[lv].indices[block_j][block_i] = previousOffset;
        }
        if(i>=blockSize&&
           j<=blockSize) // block 1
        {
          if(isNewVertex)
            this.block1.level[lv].indices[block_j][block_i] = offset;
          else
            this.block1.level[lv].indices[block_j][block_i] = previousOffset;
        }
        if(i>=blockSize&&
           j>=blockSize) // block 2
        {
          if(isNewVertex)
            this.block2.level[lv].indices[block_j][block_i] = offset;
          else
            this.block2.level[lv].indices[block_j][block_i] = previousOffset;
        }
        if(i<=blockSize&&
           j>=blockSize) // block 3
        {
          if(isNewVertex)
            this.block3.level[lv].indices[block_j][block_i] = offset;
          else
            this.block3.level[lv].indices[block_j][block_i] = previousOffset;
        }
        
        if(isNewVertex)
        {
          verticesArray[offset*3] = x;
          verticesArray[offset*3+1] = 0.;
          verticesArray[offset*3+2] = z;
          offset++;
        } 
      }
    }
    gridSpace*=2;
  }
  this.block0.calcTriangleStrip();
  this.block1.calcTriangleStrip();
  this.block2.calcTriangleStrip();
  this.block3.calcTriangleStrip();
  
  this.verticesNum = offset;
  // Calc triangle list of each vertex ( for generate average normal later)
  var trianglePerBlock = this.block0.indices.length-2;
  var verNum = this.verticesNum;
  for(var i=0;i< verNum;i++)
  {
    this.triangleList[i] = [];
  }  
  
  for(var b=0;b<4;b++)
  {
    var block;
    switch(b)
    {
      case 0:
        block = this.block0;break;
      case 1:
        block = this.block1;break;
      case 2:
        block = this.block2;break;
      case 3:
        block = this.block3;break;
    }
    var triangleOffset = trianglePerBlock*b;
    var blockIndx = block.indices;
    var indxNum = blockIndx.length;
    for(var bi=0;bi< indxNum;bi++)
    {
      var verIndx = blockIndx[bi];
      var triangleIdx = Math.max((bi-2),0)+triangleOffset;
      var isValid = true;
      if(bi>1)
      {
        i0 = blockIndx[bi-1];
        i1 = blockIndx[bi-2];
        
        var offset0 = verIndx*3;
        var offset1 = i0*3;
        var offset2 = i1*3;
        
        var x0 = verticesArray[offset0];
        var z0 = verticesArray[offset0+2];
        
        var x1 = verticesArray[offset1];
        var z1 = verticesArray[offset1+2];
        
        var x2 = verticesArray[offset2];
        var z2 = verticesArray[offset2+2];
        
        if(x1!=x2&& z1!=z2) // 0 la goc vuong
        {
          if( x0==x1&&z0==z2||
              z0==z1&&x0==x2)
          {
          }
          else
          {
            isValid = false;
          } 
        }        
        else if(x0!=x2&& z0!=z2) // 1 la goc vuong
        {
          if( x1==x0&&z1==z2||
              z1==z0&&x1==x2)
          {
          }
          else
          {
            isValid = false;
          } 
        }        
        else if(x1!=x0&& z1!=z0) // 2 la goc vuong
        {
          if( x2==x1&&z2==z0||
              z2==z1&&x2==x0 )
          {
          }
          else
          {
            isValid = false;
          } 
        }
        else
        {
          isValid = false;
        }
        
      }
      if(isValid)
        this.triangleList[verIndx].push(triangleIdx);
    }
  }
  
  var totalTriangleNum = trianglePerBlock*4;
  for(var tri=0;tri<totalTriangleNum;tri++)
  {
    this.triangleNormal[tri] = new gewVec3();
  }
  
  
  var glBuffers = this.glBuffers;
  // Create vertex buffer object
  var gl =  this.gl;
  var ver_buff = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, ver_buff);
  gl.bufferData(gl.ARRAY_BUFFER, verticesArray, gl.DYNAMIC_DRAW);
  glBuffers.position = ver_buff;
  
  // Create normal buffer object
  var nor_buff = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, nor_buff);
  gl.bufferData(gl.ARRAY_BUFFER, this.normals, gl.DYNAMIC_DRAW);
  glBuffers.normal = nor_buff;
  
  //Create indices buffer object for 4 block
  var ind_buff0 = gl.createBuffer();
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ind_buff0);
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.block0.indices, gl.STATIC_DRAW);
  glBuffers.indices0 = ind_buff0;
  
  var ind_buff1 = gl.createBuffer();
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ind_buff1);
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.block1.indices, gl.STATIC_DRAW);
  glBuffers.indices1 = ind_buff1;
  
  var ind_buff2 = gl.createBuffer();
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ind_buff2);
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.block2.indices, gl.STATIC_DRAW);
  glBuffers.indices2 = ind_buff2;
  
  var ind_buff3 = gl.createBuffer();
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ind_buff3);
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.block3.indices, gl.STATIC_DRAW);
  glBuffers.indices3 = ind_buff3;
}
gewTerrainNode.prototype.setAttributePointers = function(material)
{
  var glBuffers = this.glBuffers;
  var gl = this.gl;
  var effect = material.effect;
  switch(effect.name){
    case "TerrainEffect":
      var shader = effect.shader;
      gl.bindBuffer(gl.ARRAY_BUFFER, glBuffers.position);
      gl.vertexAttribPointer(shader.inPosition, 3, gl.FLOAT, false, 0, 0);
      gl.bindBuffer(gl.ARRAY_BUFFER, glBuffers.normal);
      gl.vertexAttribPointer(shader.inNormal, 3, gl.FLOAT, false, 0, 0);
      break;
    case "DepthMaskEffect":
      var shader = effect.shaders.terrain;
      gl.bindBuffer(gl.ARRAY_BUFFER, glBuffers.position);
      gl.vertexAttribPointer(shader.inPosition, 3, gl.FLOAT, false, 0, 0);
      break
  } 
}
gewTerrainNode.prototype.render = function(material)
{
  var glBuffers = this.glBuffers;
  var gl = this.gl;
  
  var block0_indices_count = this.indicesNumPerBlock;
  var block1_indices_count = this.indicesNumPerBlock;
  var block2_indices_count = this.indicesNumPerBlock;
  var block3_indices_count = this.indicesNumPerBlock;
     
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, glBuffers.indices0);
  gl.drawElements(gl.TRIANGLE_STRIP, block0_indices_count, gl.UNSIGNED_SHORT, 0);
  
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, glBuffers.indices1);
  gl.drawElements(gl.TRIANGLE_STRIP, block1_indices_count, gl.UNSIGNED_SHORT, 0);
  
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, glBuffers.indices2);
  gl.drawElements(gl.TRIANGLE_STRIP, block2_indices_count, gl.UNSIGNED_SHORT, 0);
  
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, glBuffers.indices3);
  gl.drawElements(gl.TRIANGLE_STRIP, block3_indices_count, gl.UNSIGNED_SHORT, 0);
}
gewTerrainNode.prototype.updateHeight = function(heightMap,tx,tz)
{
  // Update height
  var gl = this.gl;
  var vertices = this.vertices;
  var verNum = this.verticesNum;
  var w = heightMap.width;
  var h = heightMap.height;
  var hw = w/2;
  var hh = h/2;
  var dw = w*2;
  var dh = h*2;
  var data = heightMap.data[0];
  for(var i=0;i< verNum;i++)
  {
    var offset = i*3;
    var col = hw+vertices[offset]+tx;
    var row = hh+(vertices[offset+2]+tz);
    if(col<0)
      col = Math.abs(col);
    if(col>=w)
    {
      var bigger_w = Math.ceil((col+1)/w)*w;
      col = bigger_w-col;
    }
    if(row<0)
      row = Math.abs(row);
    if(row>=h)
    {
      var bigger_h = Math.ceil((row+1)/h)*h;
      row = bigger_h-row;
    }   
    vertices[offset+1] = data[col+ row*w];
  }  
  // Update triangle normals
  var trianglePerBlock = this.block0.indices.length-2;
  var triangleNormals = this.triangleNormal;
  var v0 = new gewVec3();
  var v1 = new gewVec3();
  var v2 = new gewVec3();
  var e01 = new gewVec3();
  var e02 = new gewVec3();
  var vOy = new gewVec3(0,1,0);
  var half_pi = GEW_MATH_PI/2;
  for(var b=0;b<4;b++)
  {
    var block;
    switch(b)
    {
      case 0:
        block = this.block0;break;
      case 1:
        block = this.block1;break;
      case 2:
        block = this.block2;break;
      case 3:
        block = this.block3;break;
    }
    var triangleOffset = trianglePerBlock*b;
    var blockIndx = block.indices;
    var indxNum = blockIndx.length;
    for(var bi=2;bi< indxNum;bi++)
    {
      var i0 = blockIndx[bi-2];
      var i1 = blockIndx[bi-1];
      var i2 = blockIndx[bi];
      v0.set( vertices[i0*3],vertices[i0*3+1],vertices[i0*3+2] );
      v1.set( vertices[i1*3],vertices[i1*3+1],vertices[i1*3+2] );
      v2.set( vertices[i2*3],vertices[i2*3+1],vertices[i2*3+2] );
      v1.sub(v0,e01);
      v2.sub(v0,e02);
      var triangleIdx = (bi-2)+triangleOffset;
      var normal = triangleNormals[triangleIdx];
      if(normal)
      {
        e01.cross(e02,normal);
        var angle = normal.angleFrom(vOy);
        if(angle>half_pi)
        {
          normal.set( -normal.x,-normal.y,-normal.z);
        }
      }
    } 
  }  
  // Generate average normal for each vertex  
  var normals = this.normals;
  var temp = new gewVec3();
  var vZero = new gewVec3(0,0,0);
  for(var i=0;i< verNum;i++)
  {
    var offset = i*3;
    var triList = this.triangleList[i];
    var triNum = triList.length;
    for(var tri=0;tri< triNum;tri++)
    {
      var triangleIdx = triList[tri];
      var triangleNormal = triangleNormals[triangleIdx];
      //if(!triangleNormal.isEqual(vZero) )
      temp.add(triangleNormal,temp);      
    }    
    temp.normalize();
    normals[offset] = temp.x;
    normals[offset+1] = temp.y;
    normals[offset+2] = temp.z;
  }  
  gl.bindBuffer(gl.ARRAY_BUFFER, this.glBuffers.position);
  gl.bufferSubData(gl.ARRAY_BUFFER, 0, vertices);
  gl.bindBuffer(gl.ARRAY_BUFFER, this.glBuffers.normal);
  gl.bufferSubData(gl.ARRAY_BUFFER, 0, normals)
}

function gewTerrainLevelBlock(clipmapSize)
{
  blockSize = clipmapSize/2;
  this.blockSize = blockSize;
  this.indices = new Array(blockSize+1);
  for(var i =0;i< blockSize+1;i++)
    this.indices[i] = new Array(blockSize+1);  
}
gewTerrainLevelBlock.prototype.getIndicesArray = function()
{
  var glArray = new WebGLUnsignedShortArray( (blockSize+1)*(blockSize+1));
  for(var i=0;i<=blockSize;i++)
    for(var j=0;j<=blockSize;j++)
    {
      glArray[j+ i*(blockSize+1)] = this.indices[j][i];
    }
  return glArray;
}
function gewTerrainBlock(clipmapSize,levelNum)
{
  this.level = [];
  this.blockSize = clipmapSize/2;
  this.levelNum = levelNum;
  var lv = this.level;
  for(var i=0;i<levelNum;i++)
  {
    lv[i] = new gewTerrainLevelBlock(clipmapSize);
  }
  var bs = this.blockSize;
  var indicesNumLv0  = (4*bs+3)*(bs/2)+(bs+1)+1;
  var indicesNumLvn = (3*bs/2)*(bs+1)+(bs+2) +1+1+1;
  var totalIndicesNum = indicesNumLv0+ (levelNum-1)*indicesNumLvn;
  this.indices = new WebGLUnsignedShortArray(totalIndicesNum  );
  this.indicesNumLv0 = indicesNumLv0;
  this.indicesNumLvn = indicesNumLvn;
  this.glIndicesBuffer = null;
}
gewTerrainBlock.prototype.calcTriangleStrip = function()
{
  var buff = this.indices;
  var bs = this.blockSize;
  var waveNum = bs/2;
  var offset = 0;
  var lv0IndicesTable = this.level[0].indices;
  // level 0
  for(var wave=0;wave<waveNum;wave++)
  {
    // forward wave
    for(var col =0;col<=bs;col++)
    {
      buff[offset] =  lv0IndicesTable[col][wave*2];
      buff[offset+1] =  lv0IndicesTable[col][wave*2+1];
      offset+=2;
    }
    // backward wave
    buff[offset] =  lv0IndicesTable[bs][wave*2+2];
    offset++;
    for(var col =(bs-1);col>=0;col--)
    {
      buff[offset] =  lv0IndicesTable[col][wave*2+1];
      buff[offset+1] =  lv0IndicesTable[col][wave*2+2];
      offset+=2;
    }
  }
  for(var col=0;col<=bs;col++)
  {
    buff[offset] = lv0IndicesTable[col][bs];
    offset++;
  }
  // End level0 strip
  buff[offset] = buff[offset-1];
  offset++;
  // level 1..n
  for(var lv = 1;lv<this.levelNum;lv++)
  {
    var lvIndicesTable = this.level[lv].indices;
    // End previous lv's strip
    buff[offset] = lvIndicesTable[bs/2][0];
    offset++;
    // Small part of L shape
    for(var wave=0;wave<(waveNum/2);wave++)
    {
      // forward wave
      for(var col =(bs/2);col<=bs;col++)
      {
        buff[offset] =  lvIndicesTable[col][wave*2];
        buff[offset+1] =  lvIndicesTable[col][wave*2+1];
        offset+=2;
      }
      // backward wave
      buff[offset] =  lvIndicesTable[bs][wave*2+2];
      offset++;
      for(var col =(bs-1);col>=(bs/2);col--)
      {
        buff[offset] =  lvIndicesTable[col][wave*2+1];
        buff[offset+1] =  lvIndicesTable[col][wave*2+2];
        offset+=2;
      }
    }
    // End Small part of L shape's triangle strip 
    buff[offset] = buff[offset-1];
    offset++;
    
    buff[offset] = lvIndicesTable[0][waveNum];
    offset++;
    // Main part of L shape
    for(var wave=(waveNum/2);wave<waveNum;wave++)
    {
      // forward wave
      for(var col =0;col<=bs;col++)
      {
        buff[offset] =  lvIndicesTable[col][wave*2];
        buff[offset+1] =  lvIndicesTable[col][wave*2+1];
        offset+=2;
      }
      // backward wave
      buff[offset] =  lvIndicesTable[bs][wave*2+2];
      offset++;
      for(var col =(bs-1);col>=0;col--)
      {
        buff[offset] =  lvIndicesTable[col][wave*2+1];
        buff[offset+1] =  lvIndicesTable[col][wave*2+2];
        offset+=2;
      }
    }
    for(var col=0;col<=bs;col++)
    {
      buff[offset] = lvIndicesTable[col][bs];
      offset++;
    }
    // End leveln strip
    buff[offset] = buff[offset-1];
    offset++;
  }
  this.indicesNum = offset;  
}
